
#if defined(_WIN32)

#include "W32Window.h"

namespace p2
{

/* struct used to manage color ramps */
struct colorIndexState
{
	GLfloat amb[3]; /* ambient color / bottom of ramp */
	GLfloat diff[3]; /* diffuse color / middle of ramp */
	GLfloat spec[3]; /* specular color / top of ramp */
	GLfloat ratio; /* ratio of diffuse to specular in ramp */
	GLint indexes[3]; /* where ramp was placed in palette */
};

#define NUM_COLORS (sizeof(s_colors) / sizeof(s_colors[0]))
struct colorIndexState s_colors[] =
{
	{ { 0.0F, 0.0F, 0.0F }, { 1.0F, 0.0F, 0.0F }, { 1.0F, 1.0F, 1.0F }, 0.75F, { 0, 0, 0 } },
	{ { 0.0F, 0.05F, 0.05F }, { 0.9F, 0.0F, 1.0F }, { 1.0F, 1.0F, 1.0F }, 1.0F, { 0, 0, 0 } },
	{ { 0.0F, 0.0F, 0.0F }, { 1.0F, 0.9F, 0.1F }, { 1.0F, 1.0F, 1.0F }, 0.75F, { 0, 0, 0 } },
	{ { 0.0F, 0.0F, 0.0F }, { 0.1F, 1.0F, 0.9F }, { 1.0F, 1.0F, 1.0F }, 0.75F, { 0, 0, 0 } }
};

static GLint s_lit_tex_indexes[3];

W32Window::W32Window(WindowCreateStruct const& cs)
:	Window(cs), _eglContext(NULL), _eglConfig(NULL), _eglDisplay(NULL), _eglSurface(NULL),
 	_hInstance(NULL), _hWnd(NULL), _hDC(NULL), _hglrc(NULL), _hPalette(NULL)
{
}

W32Window::~W32Window()
{
}

int W32Window::create()
{
	return this->create(_cs._name.c_str(), _cs._name.c_str());
}

int W32Window::processContinue()
{
	MSG msg;
	while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
	{
		if (GetMessage(&msg, NULL, 0, 0) != TRUE)
		{
			killGLWindow();
			return msg.wParam;
		}
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return 1;
}

static LRESULT APIENTRY wndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

void W32Window::killGLWindow(void) // Properly Kill The Window
{
	if (_cs._fullscreen) // Are We In Fullscreen Mode?
	{
		ChangeDisplaySettings(NULL, 0); // If So Switch Back To The Desktop
		ShowCursor(TRUE); // Show Mouse Pointer
	}
	//if (_hglrc) // Do We Have A Rendering Context?
	//{
	//	if (!wglMakeCurrent(NULL, NULL)) // Are We Able To Release The DC And RC Contexts?
	//		{
	//		MessageBox(NULL, "Release Of DC And RC Failed.", "SHUTDOWN ERROR",
	//			MB_OK | MB_ICONINFORMATION);
	//	}
	//	if (!wglDeleteContext(_hglrc)) // Are We Able To Delete The RC?
	//		{
	//		MessageBox(NULL, "Release Rendering Context Failed.",
	//			"SHUTDOWN ERROR", MB_OK | MB_ICONINFORMATION);
	//	}
	//	_hglrc = NULL; // Set RC To NULL
	//}

	if (_hDC && !ReleaseDC(_hWnd, _hDC)) // Are We Able To Release The DC
	{
		MessageBox(NULL, "Release Device Context Failed.", "SHUTDOWN ERROR",
				MB_OK | MB_ICONINFORMATION);
		_hDC = NULL; // Set DC To NULL
	}

	if (_hWnd && !DestroyWindow(_hWnd)) // Are We Able To Destroy The Window?
	{
		MessageBox(NULL, "Could Not Release hWnd.", "SHUTDOWN ERROR",
				MB_OK | MB_ICONINFORMATION);
		_hWnd = NULL; // Set hWnd To NULL
	}
	if (!UnregisterClass(_className.c_str(), _hInstance)) // Are We Able To Unregister Class
	{
		MessageBox(NULL, "Could Not Unregister Class.", "SHUTDOWN ERROR",
				MB_OK | MB_ICONINFORMATION);
		_hInstance = NULL; // Set hInstance To NULL
	}
}

int32 W32Window::create(char const* className, char const* windowName)
{
	RECT windowRect;
	windowRect.left = _cs._winX;
	windowRect.top = _cs._winY;
	windowRect.right = _cs._winX + _cs._winW;
	windowRect.bottom = _cs._winY + _cs._winH;

	_hInstance = GetModuleHandle(NULL);
	WNDCLASS wc;
	wc.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc = wndProc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = _hInstance;
	wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName = NULL;
	wc.lpszClassName = className;
	if (!RegisterClass(&wc))
	{
		MessageBox(NULL, "Failed To Register The Window Class.", "ERROR",
				MB_OK | MB_ICONEXCLAMATION);
		return FALSE; // Exit And Return FALSE
	}

	HDC hdc = GetDC(0);
	_bpp = GetDeviceCaps(hdc, BITSPIXEL);
	_rgba = 1;
	ReleaseDC(0, hdc);

	if (_cs._fullscreen)
	{
		DEVMODE dmScreenSettings; // Device Mode
		memset(&dmScreenSettings, 0, sizeof(dmScreenSettings)); // Makes Sure Memory's Cleared
		dmScreenSettings.dmSize = sizeof(dmScreenSettings); // Size Of The Devmode Structure
		dmScreenSettings.dmPelsWidth = _cs._winW; // Selected Screen Width
		dmScreenSettings.dmPelsHeight = _cs._winH; // Selected Screen Height
		dmScreenSettings.dmBitsPerPel = _bpp; // Selected Bits Per Pixel
		dmScreenSettings.dmFields =
				DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;

		// Try To Set Selected Mode And Get Results.  NOTE: CDS_FULLSCREEN Gets Rid Of Start Bar.
		if (ChangeDisplaySettings(&dmScreenSettings,
				CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL)
		{
			// If The Mode Fails, Offer Two Options.  Quit Or Use Windowed Mode.
			if (MessageBox(NULL,
					"The Requested Fullscreen Mode Is Not Supported By\nYour Video Card. Use Windowed Mode Instead?",
					"NeHe GL", MB_YESNO | MB_ICONEXCLAMATION) == IDYES)
			{
				_cs._fullscreen = false; // Windowed Mode Selected.  Fullscreen = FALSE
			}
			else
			{
				// Pop Up A Message Box Letting User Know The Program Is Closing.
				MessageBox(NULL, "Program Will Now Close.", "ERROR",
						MB_OK | MB_ICONSTOP);
				DWORD err = ::GetLastError();
				if (err) {
					return err;
				}
			}
		}
	}

	DWORD dwExStyle, dwStyle;
	if (_cs._fullscreen)
	{
		dwExStyle = WS_EX_APPWINDOW; // Window Extended Style
		dwStyle = WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN; // Windows Style
	}
	else
	{
		dwExStyle = WS_EX_TOPMOST; //WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;						// Window Extended Style
		dwStyle = WS_POPUP; //WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;	// Windows Style
	}
	ShowCursor(FALSE);										// Hide Mouse Pointer

	AdjustWindowRectEx(&windowRect, dwStyle, FALSE, dwExStyle); // Adjust Window To True Requested Size

	_hWnd = CreateWindowEx(dwExStyle, className, windowName, dwStyle, _cs._winX,
			_cs._winY, _cs._winW, _cs._winH, NULL, NULL, _hInstance, this);
	if (!_hWnd)
	{
		killGLWindow();
		return ::GetLastError();
	}
	_className = className;
	_cs._name = windowName;

	::SetLastError(0);
	if (!SetWindowLongPtr(_hWnd, GWLP_USERDATA,
			reinterpret_cast<LONG_PTR>(this)))
	{
		DWORD err = ::GetLastError();
		if (err)
		{
			killGLWindow();
			return err;
		}
	}
	_hDC = GetDC(_hWnd);
	if (!_hDC)
	{
		killGLWindow();
		return ::GetLastError();
	}
	if (!setPixelFormat())
	{
		killGLWindow();
		return ::GetLastError();
	}
	setPalette();

	EGLNativeDisplayType nativeDisplay = NULL;
	_eglDisplay = eglGetDisplay(nativeDisplay);
	ASSERTLOG(_eglDisplay != EGL_NO_DISPLAY, "W32Window::create");
	if (_eglDisplay == EGL_NO_DISPLAY )
	{
		return GL_FALSE;
	}

	EGLint major = 0;
	EGLint minor = 0;
	EGLBoolean bsuccess = eglInitialize(_eglDisplay, &major, &minor);
	if (!bsuccess)
	{
		printf("Could not initialize EGL display\n");
		return GL_FALSE;
	}
	if (major < 1 || minor < 4)
	{
		// Does not support EGL 1.4
		printf("System does not support at least EGL 1.4\n");
		return GL_FALSE;
	}

	// Obtain the first configuration with a depth buffer
	EGLint attrs[] = { EGL_DEPTH_SIZE, 16, EGL_NONE };
	EGLint numConfig = 0;
	_eglConfig = 0;
	bsuccess = eglChooseConfig(_eglDisplay, attrs, &_eglConfig, 1, &numConfig);
	if (!bsuccess)
	{
		printf("Could not find valid EGL config\n");
		return GL_FALSE;
	}

	// Get the native visual id
	int nativeVid;
	if (!eglGetConfigAttrib(_eglDisplay, _eglConfig, EGL_NATIVE_VISUAL_ID, &nativeVid))
	{
		printf("Could not get native visual id\n");
		return GL_FALSE;
	}

	// Create a surface for the main window
	_eglSurface = eglCreateWindowSurface(_eglDisplay, _eglConfig, _hWnd, NULL);
	if (_eglSurface == EGL_NO_SURFACE )
	{
		printf("Could not create EGL surface\n");
		//DestroyNativeWin(nativeDisplay, nativeWin);
		return GL_FALSE;
	}

	// Create an OpenGL ES context
	_eglContext = eglCreateContext(_eglDisplay, _eglConfig, EGL_NO_CONTEXT, NULL);
	if (_eglContext == EGL_NO_CONTEXT )
	{
		printf("Could not create EGL context\n");
		//DestroyNativeWin(nativeDisplay, nativeWin);
		return GL_FALSE;
	}

	// Make the context and surface current
	bsuccess = eglMakeCurrent(_eglDisplay, _eglSurface, _eglSurface, _eglContext);
	if (!bsuccess)
	{
		printf("Could not activate EGL context\n");
		//DestroyNativeWin(nativeDisplay, nativeWin);
		return GL_FALSE;
	}
	LOG("window and opengl es context created");
	return 0;
}

void W32Window::show(bool show) const
{
	ShowWindow(_hWnd, show ? SW_SHOW : SW_HIDE);
	UpdateWindow(_hWnd);
}

Vector2<real> W32Window::getCursorPosition() const
{
	POINT point;
	::GetCursorPos(&point);
	return Vector2<real>((real)point.x, (real)point.y);
}

void W32Window::swapBuffers() const
{
	eglSwapBuffers(_eglDisplay, _eglSurface);
}

bool W32Window::setPixelFormat()
{
	unsigned char pixelType = _rgba ? PFD_TYPE_RGBA : PFD_TYPE_COLORINDEX;
	PIXELFORMATDESCRIPTOR pfd =
	{ sizeof(PIXELFORMATDESCRIPTOR), 1, PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL
			| PFD_DOUBLEBUFFER, pixelType, (BYTE) _bpp, 0, 0, 0, 0, 0, 0, 0, 0,
			0, 0, 0, 0, 0, (BYTE) _depthBits, // 16-bit depth buffer
			0, // no stencil buffer
			0, // no aux buffers
			PFD_MAIN_PLANE, /* main layer */
			0, 0, 0, 0 };
	int selected_pf;
	if ((selected_pf = ChoosePixelFormat(_hDC, &pfd)) == 0)
	{
		killGLWindow();
		MessageBox(0, "Failed to find acceptable pixel format",
				"GL Sphere Error", MB_ICONERROR | MB_OK);
		return false;
	}
	if (!SetPixelFormat(_hDC, selected_pf, &pfd))
	{
		killGLWindow();
		MessageBox(0, "Failed to SetPixelFormat", "GL Sphere Error",
				MB_ICONERROR | MB_OK);
		return false;
	}
	return true;
}

static LRESULT APIENTRY wndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	W32Window &win = *reinterpret_cast<W32Window*>(GetWindowLongPtr(hWnd,
			GWLP_USERDATA));
	switch (msg)
	{
	case WM_CREATE:
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

		// process key events
	case WM_CHAR:
		switch (wParam)
		{
		case 27:
			PostQuitMessage(0);
			break;
		}
		return 0;

	case WM_KEYDOWN:
		if (win._cs._onKeyDown)
		{
			win._cs._onKeyDown(win, wParam);
		}
		return 0;
	case WM_KEYUP:
		if (win._cs._onKeyUp)
		{
			win._cs._onKeyUp(win, wParam);
		}
		return 0;

		// process cursor events
	case WM_LBUTTONDOWN:
		if (win._cs._onLButtonDown)
		{
			win._cs._onLButtonDown(win, LOWORD(lParam), HIWORD(lParam) );
		}
		return 0;
	case WM_LBUTTONUP:
		if (win._cs._onLButtonUp)
		{
			win._cs._onLButtonUp(win, LOWORD(lParam), HIWORD(lParam) );
		}
		return 0;
	case WM_RBUTTONDOWN:
		if (win._cs._onRButtonDown)
		{
			win._cs._onRButtonDown(win, LOWORD(lParam), HIWORD(lParam) );
		}
		return 0;
	case WM_RBUTTONUP:
		if (win._cs._onRButtonUp)
		{
			win._cs._onRButtonUp(win, LOWORD(lParam), HIWORD(lParam) );
		}
		return 0;
	case WM_MOUSEMOVE:
		if (win._cs._onCursorMove)
		{
			win._cs._onCursorMove(win, LOWORD(lParam), HIWORD(lParam) );
		}
		return 0;
	case WM_PALETTECHANGED:
		if (win._hPalette != NULL && (HWND) wParam != hWnd)
		{
			UnrealizeObject(win._hPalette);
			SelectPalette(win._hDC, win._hPalette, FALSE);
			RealizePalette(win._hDC);
			return 0;
		}
		break;
	case WM_QUERYNEWPALETTE:
		if (win._hPalette != NULL)
		{
			UnrealizeObject(win._hPalette);
			SelectPalette(win._hDC, win._hPalette, FALSE);
			RealizePalette(win._hDC);
			return TRUE;
		}
		return 0;

	case WM_COMMAND:
		break;
	}
	return DefWindowProc(hWnd, msg, wParam, lParam);
}

void W32Window::setPalette()
{
	PIXELFORMATDESCRIPTOR pfd;
	LOGPALETTE *pPal;
	WORD paletteSize;
	int pixelFormat = GetPixelFormat(_hDC);

	DescribePixelFormat(_hDC, pixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &pfd);

	if (!((pfd.dwFlags & PFD_NEED_PALETTE)
			|| pfd.iPixelType == PFD_TYPE_COLORINDEX))
		return;

	paletteSize = 1 << pfd.cColorBits;

	pPal = (LOGPALETTE *) malloc(
			sizeof(LOGPALETTE) + paletteSize * sizeof(PALETTEENTRY));
	pPal->palVersion = 0x300;
	pPal->palNumEntries = paletteSize;

	/* start with a copy of the current system palette */
	GetSystemPaletteEntries(_hDC, 0, paletteSize, &pPal->palPalEntry[0]);

	if (pfd.iPixelType == PFD_TYPE_RGBA)
	{
		/* fill in an RGBA color palette */
		int redMask = (1 << pfd.cRedBits) - 1;
		int greenMask = (1 << pfd.cGreenBits) - 1;
		int blueMask = (1 << pfd.cBlueBits) - 1;
		int i;

		for (i = 0; i < paletteSize; ++i)
		{
			pPal->palPalEntry[i].peRed = (BYTE) ((((i >> pfd.cRedShift)
					& redMask) * 255) / redMask);
			pPal->palPalEntry[i].peGreen = (BYTE) ((((i >> pfd.cGreenShift)
					& greenMask) * 255) / greenMask);
			pPal->palPalEntry[i].peBlue = (BYTE) ((((i >> pfd.cBlueShift)
					& blueMask) * 255) / blueMask);
			pPal->palPalEntry[i].peFlags = 0;
		}
	}
	else
	{
		/* fill in a Color Index ramp color palette */
		int numRamps = NUM_COLORS;
		int rampSize = (paletteSize - 20) / numRamps;
		int extra = (paletteSize - 20) - (numRamps * rampSize);
		int i, r;

		for (r = 0; r < numRamps; ++r)
		{
			int rampBase = r * rampSize + 10;
			PALETTEENTRY *pe = &pPal->palPalEntry[rampBase];
			int diffSize = (int) (rampSize * s_colors[r].ratio);
			int specSize = rampSize - diffSize;

			for (i = 0; i < rampSize; ++i)
			{
				GLfloat *c0, *c1;
				GLint a;

				if (i < diffSize)
				{
					c0 = s_colors[r].amb;
					c1 = s_colors[r].diff;
					a = (i * 255) / (diffSize - 1);
				}
				else
				{
					c0 = s_colors[r].diff;
					c1 = s_colors[r].spec;
					a = ((i - diffSize) * 255) / (specSize - 1);
				}

				pe[i].peRed = (BYTE) (a * (c1[0] - c0[0]) + 255 * c0[0]);
				pe[i].peGreen = (BYTE) (a * (c1[1] - c0[1]) + 255 * c0[1]);
				pe[i].peBlue = (BYTE) (a * (c1[2] - c0[2]) + 255 * c0[2]);
				pe[i].peFlags = PC_NOCOLLAPSE;
			}

			s_colors[r].indexes[0] = rampBase;
			s_colors[r].indexes[1] = rampBase + (diffSize - 1);
			s_colors[r].indexes[2] = rampBase + (rampSize - 1);
		}
		s_lit_tex_indexes[0] = 0;
		s_lit_tex_indexes[1] = (GLint) (rampSize * s_colors[0].ratio) - 1;
		s_lit_tex_indexes[2] = rampSize - 1;

		for (i = 0; i < extra; ++i)
		{
			int index = numRamps * rampSize + 10 + i;
			PALETTEENTRY *pe = &pPal->palPalEntry[index];

			pe->peRed = (BYTE) 0;
			pe->peGreen = (BYTE) 0;
			pe->peBlue = (BYTE) 0;
			pe->peFlags = PC_NOCOLLAPSE;
		}
	}

	_hPalette = CreatePalette(pPal);
	free(pPal);

	if (_hPalette)
	{
		SelectPalette(_hDC, _hPalette, FALSE);
		RealizePalette(_hDC);
	}
}

}

#endif	//#if defined(_WIN32)
